package com.atguigu.tingshu.common.login;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 认证校验切面类：对自定义注解@GuiguLogin修饰方法进行增强，获取登录用户ID，执行不同业务
 * 1.声明切面类-产生对象
 * 2.声明通知
 * 3.声明切入点（对注解进行增强）
 * 4.完善认证通知代码，尝试获取登录用户ID
 *
 * @author: atguigu
 * @create: 2023-10-20 11:15
 */
@Slf4j
@Aspect //声明切面
@Component //将切面对象注册到IOC容器
public class GuiGuLoginAspect {


    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * @param joinPoint  切入点方法对象
     * @param guiGuLogin 自定义注解对象
     * @return
     */
    @SneakyThrows //自动对方法体try catch
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(guiGuLogin)")
    public Object guiguLoginAspect(ProceedingJoinPoint joinPoint, GuiGuLogin guiGuLogin) {
        Object object = new Object();
        log.info("前置通知逻辑...");
        //1.尝试从请求对象中获取用户Token（请求头）
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //RequestAttributes是接口  ServletRequestAttributes接口实现类
        ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;

        HttpServletRequest request = sra.getRequest();
        //HttpServletResponse response = sra.getResponse();

        //2.根据Token获取用户信息（用户ID，用户昵称）
        //2.1 获取用户token
        String token = request.getHeader("token");
        //2.2 拼接用户登录时候存入Redis中Key
        String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        //2.3 根据Key查询用户信息
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(loginKey);

        if (guiGuLogin.required()) {
            //要求用户必须登录才可以，如果此时用户信息为空抛出异常，小程序员引导用户进行登录
            if (userInfoVo == null) {
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            }
        }

        //3.将用户信息隐式传入，在当前线程生命周期内获取到用户信息
        if (userInfoVo != null) {
            //说明用户登录-将用户ID跟用户名称存入ThreadLocal
            AuthContextHolder.setUserId(userInfoVo.getId());
        }

        //执行目标方法->切入点方法（被增强方法）
        object = joinPoint.proceed();

        log.info("后置通知逻辑...");
        //4.避免ThreadLocal导致内存泄漏，产生OOM问题，立即将ThreadLocal中数据清理
        AuthContextHolder.removeUserId();
        return object;
    }

}
